#pragma once

#include <cmath>
#include <cassert>

#include "constants.h"
#include "functions.h"

namespace roy {

    namespace math {

        template <typename T>
            class vec3
        {
//------------------------------------------------------------------------------------------------------------
// Static Members
//------------------------------------------------------------------------------------------------------------
        public:
            static vec3 Zero(void)
            {
                vec3<T> ret;
                return ret;
            }

            static vec3 XAxis(void)
            {
                return Axis(0);
            }

            static vec3 YAxis(void)
            {
                return Axis(1);
            }

            static vec3 ZAxis(void)
            {
                return Axis(2);
            }

            static vec3 Axis(int idx)
            {
                vec3<T> ret;
                ret[idx] = one<T>();
                return ret;
            }

        public:

            //------------------------------------------------------------------------------------------------------------
            // Default Ctor 
            //------------------------------------------------------------------------------------------------------------
            vec3(void)
            {
                x = y = z = zero<T>();
            }

            //------------------------------------------------------------------------------------------------------------
            // Initializer
            //------------------------------------------------------------------------------------------------------------
            vec3(const T& x, const T& y, const T& z)
            {
                this->x = x;
                this->y = y;
                this->z = z;
            }

            vec3(const T a[3])
            {
                this->x = a[0];
                this->y = a[1];
                this->z = a[2];
            }

            //------------------------------------------------------------------------------------------------------------
            // Copy Ctor
            //------------------------------------------------------------------------------------------------------------
            vec3(const vec3& another)
            {
                x = another.x;
                y = another.y;
                z = another.z;
            }

//------------------------------------------------------------------------------------------------------------
// Unitary Operations
//------------------------------------------------------------------------------------------------------------

            //------------------------------------------------------------------------------------------------------------
            // Forced Conversion (T*)
            //------------------------------------------------------------------------------------------------------------
            operator T* (void)
            {
                return m;
            }

            operator const T* (void) const
            {
                return m;
            }

            //------------------------------------------------------------------------------------------------------------
            // Forced Conversion (T)
            //------------------------------------------------------------------------------------------------------------
            operator T (void) const
            {
                return sqrt(x * x + y * y + z * z);
            }

            //------------------------------------------------------------------------------------------------------------
            // Negative 
            //------------------------------------------------------------------------------------------------------------
            vec3 operator - (void)
            {
                return vec3(-x, -y, -z);
            }

//------------------------------------------------------------------------------------------------------------
// Binary Operations
//------------------------------------------------------------------------------------------------------------

            //------------------------------------------------------------------------------------------------------------
            // Copy
            //------------------------------------------------------------------------------------------------------------
            vec3& operator = (const vec3& another)
            {
                x = another.x;
                y = another.y;
                z = another.z;
                return *this;
            }

            //------------------------------------------------------------------------------------------------------------
            // Test Equality
            //------------------------------------------------------------------------------------------------------------
            bool operator == (const vec3& another) const
            {
                return is_eq(x, another.x) && is_eq(y, another.y) && is_eq(z, another.z);
            }

            bool operator != (const vec3& another) const
            {
                return !(*this == another);
            }

            //------------------------------------------------------------------------------------------------------------
            // Array Selection
            //------------------------------------------------------------------------------------------------------------
            T& operator [] (int index)
            {
                return (T&) m[index];
            }

            T operator [] (int index) const
            {
                return m[index];
            }

            //------------------------------------------------------------------------------------------------------------
            // Addition
            //------------------------------------------------------------------------------------------------------------
            vec3 operator + (const vec3& another) const
            {
                return vec3(x + another.x, y + another.y, z + another.z);
            }

            vec3& operator += (const vec3& another)
            {
                x += another.x;
                y += another.y;
                z += another.z;
                return *this;
            }

            //------------------------------------------------------------------------------------------------------------
            // Subtraction
            //------------------------------------------------------------------------------------------------------------
            vec3 operator - (const vec3& another) const
            {
                return vec3(x - another.x, y - another.y, z - another.z);
            }

            vec3& operator -= (const vec3& another)
            {
                x -= another.x;
                y -= another.y;
                z -= another.z;
                return *this;
            }

            //------------------------------------------------------------------------------------------------------------
            // Numerical Product
            //------------------------------------------------------------------------------------------------------------
            vec3 operator * (T a) const
            {
                return vec3(x * a, y * a, z * a);
            }

            vec3& operator *= (T a)
            {
                x *= a;
                y *= a;
                z *= a;
                return *this;
            }

            //------------------------------------------------------------------------------------------------------------
            // Dot Product
            //------------------------------------------------------------------------------------------------------------
            T operator * (const vec3& another) const
            {
                return x * another.x
                    + y * another.y
                    + z * another.z;
            }

            //------------------------------------------------------------------------------------------------------------
            // Cross Product
            //------------------------------------------------------------------------------------------------------------
            vec3 operator ^ (const vec3& another) const
            {
                return vec3(y * another.z - z * another.y,
                    z * another.x - x * another.z,
                    x * another.y - y * another.x);
            }

            vec3& operator ^= (const vec3& another)
            {
                x = y * another.z - z * another.y;
                y = z * another.x - x * another.z;
                z = x * another.y - y * another.x;
                return *this;
            }

            //------------------------------------------------------------------------------------------------------------
            // Numerical Division
            //------------------------------------------------------------------------------------------------------------
            vec3 operator / (const T& a) const
            {
                return *this * (1.0f / a);
            }

            vec3& operator /= (const T& a)
            {
                return *this *= (1.0f / a);
            }

//------------------------------------------------------------------------------------------------------------
// Utility Functions 
//------------------------------------------------------------------------------------------------------------

            //------------------------------------------------------------------------------------------------------------
            // Normalize 
            //------------------------------------------------------------------------------------------------------------
            inline T _norm() {
                T m = (T)(*this);
                assert(is_positive(m));
                *this *= (one<T>() / m);
                return m;
            }
            inline vec3& _norm_v() {
                _norm();
                return *this;
            }
            inline vec3 norm_v() const {
                vec3 v = *this;
                v._norm();
                return v;
            }
            inline T _norm_ex(vec3 &v) {
                float m = _norm();
                v = *this;
                return m;
            }
            inline T norm_ex(vec3 &v) const {
                v = *this;
                return v._norm();
            }


//------------------------------------------------------------------------------------------------------------
// Fields 
//------------------------------------------------------------------------------------------------------------
        public:
            //T      m[3];
            //T&     x;
            //T&     y;
            //T&     z;
			union
			{	struct{ T m[3]; };
				struct{ T x,y,z; };
			};

        };


        typedef vec3<float> vec3f;
        typedef vec3<double> vec3d;

    }

}
